home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
1843
/
1843.xpi
/
content
/
firebug
/
tabCache.js
< prev
next >
Wrap
Text File
|
2010-01-15
|
17KB
|
521 lines
/* See license.txt for terms of usage */
FBL.ns(function() { with (FBL) {
// ************************************************************************************************
// Constants
const Cc = Components.classes;
const Ci = Components.interfaces;
const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
const prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch2);
const versionChecker = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
const appInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
// Maximum cached size of a signle response (bytes)
var responseSizeLimit = 1024 * 1024 * 5;
// List of text content types. These content-types are cached.
var contentTypes =
{
"text/plain": 1,
"text/html": 1,
"text/xml": 1,
"text/xsl": 1,
"text/xul": 1,
"text/css": 1,
"text/sgml": 1,
"text/rtf": 1,
"text/x-setext": 1,
"text/richtext": 1,
"text/javascript": 1,
"text/jscript": 1,
"text/tab-separated-values": 1,
"text/rdf": 1,
"text/xif": 1,
"text/ecmascript": 1,
"text/vnd.curl": 1,
"text/x-json": 1,
"text/x-js": 1,
"text/js": 1,
"text/vbscript": 1,
"view-source": 1,
"view-fragment": 1,
"application/xml": 1,
"application/xhtml+xml": 1,
"application/vnd.mozilla.xul+xml": 1,
"application/javascript": 1,
"application/x-javascript": 1,
"application/x-httpd-php": 1,
"application/rdf+xml": 1,
"application/ecmascript": 1,
"application/http-index-format": 1,
"application/json": 1,
"application/x-js": 1,
"multipart/mixed" : 1,
"multipart/x-mixed-replace" : 1
};
// ************************************************************************************************
// Model implementation
/**
* Implementation of cache model. The only purpose of this object is to register an HTTP
* observer so, HTTP communication can be interecepted and all incoming data stored within
* a cache.
*/
Firebug.TabCacheModel = extend(Firebug.Module,
{
dispatchName: "tabCache",
contentTypes: contentTypes,
fbListeners: [],
initializeUI: function(owner)
{
responseSizeLimit = Firebug.getPref(Firebug.prefDomain, "cache.responseLimit");
// Read additional text mime-types from preferences.
var mimeTypes = Firebug.getPref(Firebug.prefDomain, "cache.mimeTypes");
if (mimeTypes)
{
var list = mimeTypes.split(" ");
for (var i=0; i<list.length; i++)
contentTypes[list[i]] = 1;
}
// Register for HTTP events.
if (Ci.nsITraceableChannel)
httpObserver.addObserver(this, "firebug-http-event", false);
},
shutdown: function()
{
if (Ci.nsITraceableChannel)
httpObserver.removeObserver(this, "firebug-http-event");
},
initContext: function(context)
{
},
/* nsIObserver */
observe: function(subject, topic, data)
{
try
{
if (!(subject instanceof Ci.nsIHttpChannel))
return;
// XXXjjb this same code is in net.js, better to have it only once
var win = getWindowForRequest(subject);
if (win)
var tabId = Firebug.getTabIdForWindow(win);
if (!tabId)
return;
if (topic == "http-on-modify-request")
this.onModifyRequest(subject, win, tabId);
else if (topic == "http-on-examine-response")
this.onExamineResponse(subject, win, tabId);
else if (topic == "http-on-examine-cached-response")
this.onCachedResponse(subject, win, tabId);
}
catch (err)
{
}
},
onModifyRequest: function(request, win, tabId)
{
},
onExamineResponse: function(request, win, tabId)
{
this.registerStreamListener(request, win);
},
onCachedResponse: function(request, win, tabId)
{
this.registerStreamListener(request, win);
},
registerStreamListener: function(request, win)
{
try
{
// Due to #489317, the content type must be checked in onStartRequest
//if (!this.shouldCacheRequest(request))
// return;
request.QueryInterface(Ci.nsITraceableChannel);
// Create Firebug's stream listener that is tracing HTTP responses.
var newListener = CCIN("@joehewitt.com/firebug-channel-listener;1", "nsIStreamListener");
newListener.wrappedJSObject.window = win;
// Set proxy listener for back notifiction from XPCOM to chrome (using real interface
// so nsIRequest object is properly passed from XPCOM scope).
newListener.QueryInterface(Ci.nsITraceableChannel);
newListener.setNewListener(new ChannelListenerProxy(win));
// Add tee listener into the chain of request stream listeners so, the chain
// doesn't include a JS code. This way all exceptions are propertly distributed
// (#515051).
// The nsIStreamListenerTee differes in following branches:
// 1.9.1: not possible to registere nsIRequestObserver with tee
// 1.9.2: implements nsIStreamListenerTee_1_9_2 with initWithObserver methods
// 1.9.3: adds third parameter to the existing init method.
if (versionChecker.compare(appInfo.version, "3.6*") >= 0)
{
var tee = CCIN("@mozilla.org/network/stream-listener-tee;1", "nsIStreamListenerTee");
tee = tee.QueryInterface(Ci.nsIStreamListenerTee);
if (Ci.nsIStreamListenerTee_1_9_2)
tee = tee.QueryInterface(Ci.nsIStreamListenerTee_1_9_2);
// The response will be written into the outputStream of this pipe.
// Both ends of the pipe must be blocking.
var sink = CCIN("@mozilla.org/pipe;1", "nsIPipe");
sink.init(true, true, 0, 0, null);
var originalListener = request.setNewListener(tee);
newListener.wrappedJSObject.sink = sink;
if (tee.initWithObserver)
tee.initWithObserver(originalListener, sink.outputStream, newListener);
else
tee.init(originalListener, sink.outputStream, newListener);
}
else
{
newListener.wrappedJSObject.listener = request.setNewListener(newListener);
}
// xxxHonza: this is a workaround for #489317. Just passing
// shouldCacheRequest method to the component so, onStartRequest
// can decide whether to cache or not.
newListener.wrappedJSObject.shouldCacheRequest = function(request)
{
try {
return Firebug.TabCacheModel.shouldCacheRequest(request)
} catch (err) {}
return false;
}
// xxxHonza: this is a workaround for the tracing-listener to get the
// right context. Notice that if the window (parent browser) is closed
// during the response download the TabWatcher (used within this method)
// is undefined. But in such a case no cache is needed anyway.
// Another thing is that the context isn't available now, but will be
// as soon as this method is used from the stream listener.
newListener.wrappedJSObject.getContext = function(win)
{
try {
return TabWatcher.getContextByWindow(win);
} catch (err){}
return null;
}
}
catch (err)
{
}
},
shouldCacheRequest: function(request)
{
request.QueryInterface(Ci.nsIHttpChannel);
// Allow to customize caching rules.
if (dispatch2(this.fbListeners, "shouldCacheRequest", [request]))
return true;
// Cache only text responses for now.
var contentType = request.contentType;
if (contentType)
contentType = contentType.split(";")[0];
contentType = trim(contentType);
if (contentTypes[contentType])
return true;
// Hack to work around application/octet-stream for js files (2063).
// Let's cache all files with js extensions.
var extension = getFileExtension(safeGetName(request));
if (extension == "js")
return true;
return false;
},
});
// ************************************************************************************************
/**
* This cache object is intended to cache all responses made by a specific tab.
* The implementation is based on nsITraceableChannel interface introduced in
* Firefox 3.0.4. This interface allows to intercept all incoming HTTP data.
*
* This object replaces the SourceCache, which still exist only for backward
* compatibility.
*
* The object is derived from SourceCache so, the same interface and most of the
* implementation is used.
*/
Firebug.TabCache = function(context)
{
Firebug.SourceCache.call(this, context);
};
Firebug.TabCache.prototype = extend(Firebug.SourceCache.prototype,
{
responses: [], // responses in progress.
storePartialResponse: function(request, responseText, win)
{
try
{
responseText = FBL.convertToUnicode(responseText, win.document.characterSet);
}
catch (err)
{
}
var url = safeGetName(request);
var response = this.getResponse(request);
// Size of each response is limited.
var limitNotReached = true;
if (response.size + responseText.length >= responseSizeLimit)
{
limitNotReached = false;
responseText = responseText.substr(0, responseSizeLimit - response.size);
FBTrace.sysout("tabCache.storePartialResponse Max size limit reached for: " + url);
}
response.size += responseText.length;
// Store partial content into the cache.
this.store(url, responseText);
// Return false if furhter parts of this response should be ignored.
return limitNotReached;
},
getResponse: function(request)
{
var url = safeGetName(request);
var response = this.responses[url];
if (!response)
{
this.invalidate(url);
this.responses[url] = response = {
request: request,
size: 0
};
}
return response;
},
storeSplitLines: function(url, lines)
{
var currLines = this.cache[url];
if (!currLines)
currLines = this.cache[url] = [];
// Join the last line with the new first one so, the source code
// lines are properly formatted...
if (currLines.length)
{
// ... but only if the last line isn't already completed.
var lastLine = currLines[currLines.length-1];
if (lastLine && lastLine.search(/\r|\n/) == -1)
currLines[currLines.length-1] += lines.shift();
}
// Append new lines (if any) into the array for specified url.
if (lines.length)
this.cache[url] = currLines.concat(lines);
return this.cache[url];
},
loadFromCache: function(url, method, file)
{
// The ancestor implementation (SourceCache) uses ioService.newChannel, which
// can result in additional request to the server (in case the response can't
// be loaded from the Firefox cache) - known as double-load problem.
// This new implementation (TabCache) uses nsITraceableChannel so, all responses
// should be already cached.
// xxxHonza: TODO entire implementation of this method should be removed in Firebug 1.5
// xxxHonza: let's try to get the response from the cache till #449198 is fixed.
var stream;
var responseText;
try
{
if (!url)
return responseText;
var channel = ioService.newChannel(url, null, null);
// These flag combination doesn't repost the request.
channel.loadFlags = Ci.nsIRequest.LOAD_FROM_CACHE |
Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
var charset = "UTF-8";
var doc = this.context.window.document;
if (doc)
charset = doc.characterSet;
stream = channel.open();
// The response doesn't have to be in the browser cache.
if (!stream.available())
{
stream.close();
return [$STR("message.Failed to load source for") + ": " + url];
}
// Don't load responses that shouldn't be cached.
if (!Firebug.TabCacheModel.shouldCacheRequest(channel))
{
stream.close();
return [$STR("message.The resource from this URL is not text") + ": " + url];
}
responseText = readFromStream(stream, charset);
responseText = this.store(url, responseText);
}
catch (err)
{
}
finally
{
if (stream)
stream.close();
}
return responseText;
},
// nsIStreamListener - callbacks from channel stream listener component.
onStartRequest: function(request, requestContext)
{
var response = this.getResponse(request);
dispatch(Firebug.TabCacheModel.fbListeners, "onStartRequest", [this.context, request]);
dispatch(this.fbListeners, "onStartRequest", [this.context, request]);
},
onDataAvailable: function(request, requestContext, inputStream, offset, count)
{
var stream = {
value: inputStream
};
dispatch(Firebug.TabCacheModel.fbListeners, "onDataAvailable",
[this.context, request, requestContext, stream, offset, count]);
dispatch(this.fbListeners, "onDataAvailable", [this.context,
request, requestContext, stream, offset, count]);
return stream.value;
},
onStopRequest: function(request, requestContext, statusCode)
{
// The response is finally received so, remove the request from the list of
// current responses.
var url = safeGetName(request);
delete this.responses[url];
var lines = this.cache[this.removeAnchor(url)];
var responseText = lines ? lines.join("") : "";
dispatch(Firebug.TabCacheModel.fbListeners, "onStopRequest", [this.context, request, responseText]);
dispatch(this.fbListeners, "onStopRequest", [this.context, request, responseText]);
}
});
// ************************************************************************************************
// Proxy Listener
function ChannelListenerProxy(win)
{
this.wrappedJSObject = this;
this.window = win;
}
ChannelListenerProxy.prototype =
{
/* nsIStreamListener */
onStartRequest: function(request, requestContext)
{
var context = this.getContext();
if (context)
context.sourceCache.onStartRequest(request, requestContext);
},
onDataAvailable: function(request, requestContext, inputStream, offset, count)
{
var context = this.getContext();
if (!context)
return null;
return context.sourceCache.onDataAvailable(request, requestContext, inputStream, offset, count);
},
onStopRequest: function(request, requestContext, statusCode)
{
var context = this.getContext();
if (context)
context.sourceCache.onStopRequest(request, requestContext, statusCode);
},
/* nsISupports */
QueryInterface: function(iid)
{
if (iid.equals(Ci.nsIStreamListener) ||
iid.equals(Ci.nsISupportsWeakReference) ||
iid.equals(Ci.nsISupports))
{
return this;
}
throw Components.results.NS_NOINTERFACE;
},
getContext: function()
{
try {
return TabWatcher.getContextByWindow(this.window);
} catch (e) {}
return null;
}
}
// ************************************************************************************************
// Helpers
function safeGetName(request)
{
try {
return request.name;
}
catch (exc) {
}
return null;
}
// ************************************************************************************************
// Registration
Firebug.registerModule(Firebug.TabCacheModel);
// ************************************************************************************************
}});